package com.paradox.aggregation.provider.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.IntConsumer;

/**
 * 现有函数 printNumber 可以用一个整数参数调用，并输出该整数到控制台。
 *
 * 例如，调用 printNumber(7) 将会输出 7 到控制台。
 * 给你类 ZeroEvenOdd 的一个实例，该类中有三个函数：zero、even 和 odd 。ZeroEvenOdd 的相同实例将会传递给三个不同线程：
 *
 * 线程 A：调用 zero() ，只输出 0
 * 线程 B：调用 even() ，只输出偶数
 * 线程 C：调用 odd() ，只输出奇数
 * 修改给出的类，以输出序列 "010203040506..." ，其中序列的长度必须为 2n 。
 *
 * 实现 ZeroEvenOdd 类：
 *
 * ZeroEvenOdd(int n) 用数字 n 初始化对象，表示需要输出的数。
 * void zero(printNumber) 调用 printNumber 以输出一个 0 。
 * void even(printNumber) 调用printNumber 以输出偶数。
 * void odd(printNumber) 调用 printNumber 以输出奇数。
 *  
 *
 * 示例 1：
 *
 * 输入：n = 2
 * 输出："0102"
 * 解释：三条线程异步执行，其中一个调用 zero()，另一个线程调用 even()，最后一个线程调用odd()。正确的输出为 "0102"。
 * 示例 2：
 *
 * 输入：n = 5
 * 输出："0102030405"
 *  
 *
 * 提示：
 *
 * 1 <= n <= 1000
 *
 * 来源：力扣（LeetCode）
 * 链接：https://leetcode-cn.com/problems/print-zero-even-odd
 * 著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。
 */
public class ZeroEvenOdd {
    private int n;
    private int state=0;
    private ReentrantLock lock=new ReentrantLock();
    private Condition printZero=lock.newCondition();
    private Condition printEven=lock.newCondition();
    private Condition printOdd=lock.newCondition();
    public ZeroEvenOdd(int n) {
        this.n = n;
    }

    // printNumber.accept(x) outputs "x", where x is an integer.
    public void zero(IntConsumer printNumber) throws InterruptedException {
        lock.lock();
        try {
            while (state < n) {
                printNumber.accept(0);
                state++;
                if (state % 2 == 0) {
                    printEven.signal();
                } else {
                    printOdd.signal();
                }
                printZero.await();
            }
            printEven.signal();
            printOdd.signal();
        } finally {
            lock.unlock();
        }
    }

    public void even(IntConsumer printNumber) throws InterruptedException {
        lock.lock();
        if(state==0 || state%2==1) printEven.await();
        try{
            while(state<=n){
                if(state%2==0) printNumber.accept(state);
                printZero.signal();
                if(state==n) break;
                printEven.await();
            }
        }finally{
            lock.unlock();
        }
    }

    public void odd(IntConsumer printNumber) throws InterruptedException {
        lock.lock();
        if(state%2==0) printOdd.await();
        try{
            while(state<=n){
                if(state%2==1) printNumber.accept(state);
                printZero.signal();
                if(state==n) break;
                printOdd.await();
            }  
        }finally{
            lock.unlock();
        }
        
    }

    private static void print(int num){
        System.out.print(num);
    }

    public static void main(String[] args) {
        ZeroEvenOdd o=new ZeroEvenOdd(9);

        new Thread(()->{
            try {
                o.zero(i -> print(i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
            try {
                o.even(i -> print(i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
            try {
                o.odd(i -> print(i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}